// ----------------------------------------------------------------------------
// Copyright 2006, Martin D. Flynn
// All rights reserved
// ----------------------------------------------------------------------------
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
// http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ----------------------------------------------------------------------------
// Description:
//  This class handles the management of properties for the client.
// ----------------------------------------------------------------------------
// Change History:
//  2006/03/26  Martin D. Flynn
//      Initial release
//  2006/05/07  Martin D. Flynn
//      Added PROP_CMD_GEOF_ADMIN property.
//      Added PROP_GEOF_VERSION property.
//      Added PROP_GEOF_COUNT property.
//  2006/07/02  Martin D. Flynn
//      Made PROP_STATE_ACCOUNT_ID and PROP_STATE_DEVICE_ID writable
// ----------------------------------------------------------------------------
package org.opendmtp.j2me.client.base;

import java.util.Hashtable;
import java.util.Enumeration;
import java.util.Vector;

import org.opendmtp.j2me.codes.ClientErrors;
import org.opendmtp.j2me.codes.DMTPProps;
import org.opendmtp.j2me.util.GeoPoint;
import org.opendmtp.j2me.util.KeyValue;
import org.opendmtp.j2me.util.Payload;
import org.opendmtp.j2me.util.StringTools;
import org.opendmtp.j2me.util.Log;

/**
 * Representation of Client Props.
 * 
 * @author Martin D. Flynn
 * @author Elayne Man
 */
public class Props implements DMTPProps {

  /**
   * Log Name.
   */
  private static final String LOG_NAME = "PROPS";

  /**
   * Protocol version. This should mirror the protocol documentation version
   */
  private static final String DFT_PROTOCOL_VERSION = "0,1,1";

  /**
   * Firmware version. The arbitrary version for this code base
   */
  private static final String DFT_FIRMWARE_VERSION = "DMTP_J_1.1.2";

  /**
   * Copyright information.
   */
  private static final String DFT_COPYRIGHT = "";

  /**
   * Serial number.
   */
  private static final String DFT_SERIAL_NUMBER = "";

  /**
   * Unique ID.
   */
  private static final String DFT_UNIQUE_ID = "0x000000000000";

  /**
   * Account ID.
   */
  private static final String DFT_ACCOUNT_ID = "";

  /**
   * Device ID.
   */
  private static final String DFT_DEVICE_ID = "";

  /**
   * Communications settings.
   */
  private static final String DFT_COMM_SETTINGS = "";

  /**
   * Communications host.
   */
  private static final String DFT_COMM_HOST = "localhost";

  /**
   * Communications port.
   */
  private static final String DFT_COMM_PORT = "31000";

  /**
   * Communications DNS IP address.
   */
  private static final String DFT_COMM_DNS_1 = "127.0.0.1";

  /**
   * Communications DNS IP address.
   */
  private static final String DFT_COMM_DNS_2 = "127.0.0.1";

  /**
   * Communications APN Name.
   */
  private static final String DFT_COMM_APN_NAME = "";

  /**
   * Communications APN Server.
   */
  private static final String DFT_COMM_APN_SERVER = "";

  /**
   * Communications APN User.
   */
  private static final String DFT_COMM_APN_USER = "";

  /**
   * Communcations APN Password.
   */
  private static final String DFT_COMM_APN_PASSWORD = "";

  /**
   * Communcations APN settings.
   */
  private static final String DFT_COMM_APN_SETTINGS = "";

  /**
   * Error for invalid key.
   */
  public static final int ERROR_INVALID_KEY = 1;

  /**
   * Error for invalid type.
   */
  public static final int ERROR_INVALID_TYPE = 2;

  /**
   * Error for invalid length.
   */
  public static final int ERROR_INVALID_LENGTH = 3;

  /**
   * Error for read only.
   */
  public static final int ERROR_READ_ONLY = 11;

  /**
   * Error for write only.
   */
  public static final int ERROR_WRITE_ONLY = 12;

  /**
   * Error for command invalid.
   */
  public static final int ERROR_COMMAND_INVALID = 22;

  /**
   * Error for command error.
   */
  public static final int ERROR_COMMAND_ERROR = 23;

  /**
   * Maximum ID size.
   */
  public static final int MAX_ID_SIZE = 20; // Account/Device/Serial

  /**
   * Read only key value.
   */
  private static final int RO = KeyValue.READONLY;

  /**
   * Write only key value.
   */
  private static final int WO = KeyValue.WRITEONLY;

  /**
   * Save key value.
   */
  private static final int SAVE = KeyValue.SAVE;

  /**
   * Props Key values.
   */
  private static KeyValue kvProps[] = {
      // KeyCode Name Type Attr Ndx Init

      // --- local serial port configuration
      new KeyValue(PROP_CFG_XPORT_PORT, "cfg.xpo.port", KeyValue.STRING, RO, 1, ""),
      new KeyValue(PROP_CFG_XPORT_DEBUG, "cfg.xpo.debug", KeyValue.BOOLEAN, RO, 1, "false"),
      new KeyValue(PROP_CFG_GPS_PORT, "cfg.gps.port", KeyValue.STRING, RO, 1, ""),
      new KeyValue(PROP_CFG_GPS_MODEL, "cfg.gps.model", KeyValue.STRING, RO, 1, ""),
      new KeyValue(PROP_CFG_GPS_DEBUG, "cfg.gps.debug", KeyValue.BOOLEAN, RO, 1, "false"),
      new KeyValue(PROP_CFG_SERIAL0_PORT, "cfg.sp0.port", KeyValue.STRING, RO, 1, ""),
      new KeyValue(PROP_CFG_SERIAL0_DEBUG, "cfg.sp0.debug", KeyValue.BOOLEAN, RO, 1, "false"),
      new KeyValue(PROP_CFG_SERIAL1_PORT, "cfg.sp1.port", KeyValue.STRING, RO, 1, ""),
      new KeyValue(PROP_CFG_SERIAL1_DEBUG, "cfg.sp1.debug", KeyValue.BOOLEAN, RO, 1, "false"),

      // --- commands
      new KeyValue(PROP_CMD_STATUS_EVENT, "cmd.status", KeyValue.COMMAND, WO, 1, null),
      new KeyValue(PROP_CMD_SET_OUTPUT, "cmd.output", KeyValue.COMMAND, WO, 1, null),

      // --- retained state properties
      new KeyValue(PROP_STATE_PROTOCOL, "sta.proto", KeyValue.UINT8, RO, 3, DFT_PROTOCOL_VERSION),
      new KeyValue(PROP_STATE_FIRMWARE, "sta.firm", KeyValue.STRING, RO, 1, DFT_FIRMWARE_VERSION),
      new KeyValue(PROP_STATE_COPYRIGHT, "sta.cpywrt", KeyValue.STRING, RO, 1, DFT_COPYRIGHT),
      new KeyValue(PROP_STATE_SERIAL, "sta.serial", KeyValue.STRING, RO, 1, DFT_SERIAL_NUMBER),
      new KeyValue(PROP_STATE_UNIQUE_ID, "sta.unique", KeyValue.BINARY, RO, 1, DFT_UNIQUE_ID),
      new KeyValue(PROP_STATE_ACCOUNT_ID, "sta.account", KeyValue.STRING, SAVE, 1, DFT_ACCOUNT_ID), // allow
      // write
      new KeyValue(PROP_STATE_DEVICE_ID, "sta.device", KeyValue.STRING, SAVE, 1, DFT_DEVICE_ID), // allow
      // write
      new KeyValue(PROP_STATE_TIME, "sta.time", KeyValue.UINT32, RO, 1, "0"),
      new KeyValue(PROP_STATE_GPS, "sta.gpsloc", KeyValue.GPS, RO | SAVE, 1, "0.00000/0.00000"),
      new KeyValue(PROP_STATE_GPS_DIAGNOSTIC, "sta.gpsdiag", KeyValue.UINT32, RO, 5, "0,0,0,0,0"),
      new KeyValue(PROP_STATE_QUEUED_EVENTS, "sta.evtqueue", KeyValue.UINT32, RO, 1, "0"),

      // --- Communication properties
      new KeyValue(PROP_COMM_SPEAK_FIRST, "com.first", KeyValue.BOOLEAN, SAVE, 1, "true"),
      new KeyValue(PROP_COMM_FIRST_BRIEF, "com.brief", KeyValue.BOOLEAN, SAVE, 1, "false"),
      new KeyValue(PROP_COMM_MAX_CONNECTIONS, "com.maxconn", KeyValue.UINT8, SAVE, 3, "4,1,120"), // total/duplex/minutes
      new KeyValue(PROP_COMM_MIN_XMIT_DELAY, "com.mindelay", KeyValue.UINT16, SAVE, 1, "1800"),
      new KeyValue(PROP_COMM_MIN_XMIT_RATE, "com.minrate", KeyValue.UINT32, SAVE, 1, "1800"),
      new KeyValue(PROP_COMM_MAX_XMIT_RATE, "com.maxrate", KeyValue.UINT32, SAVE, 1, "3600"),
      new KeyValue(PROP_COMM_MAX_DUP_EVENTS, "com.maxduplex", KeyValue.UINT8, SAVE, 1, "12"),
      new KeyValue(PROP_COMM_MAX_SIM_EVENTS, "com.maxsimplex", KeyValue.UINT8, SAVE, 1, "4"),
      new KeyValue(PROP_COMM_SETTINGS, "com.settings", KeyValue.STRING, SAVE, 1, DFT_COMM_SETTINGS),
      new KeyValue(PROP_COMM_DMTP_HOST, "com.dmtp.host", KeyValue.STRING, SAVE, 1, DFT_COMM_HOST),
      new KeyValue(PROP_COMM_DMTP_PORT, "com.dmtp.port", KeyValue.UINT16, SAVE, 1, DFT_COMM_PORT),
      new KeyValue(PROP_COMM_DNS_1, "com.dns1", KeyValue.STRING, SAVE, 1, DFT_COMM_DNS_1),
      new KeyValue(PROP_COMM_DNS_2, "com.dns2", KeyValue.STRING, SAVE, 1, DFT_COMM_DNS_2),
      new KeyValue(PROP_COMM_APN_NAME, "com.apnname", KeyValue.STRING, SAVE, 1, DFT_COMM_APN_NAME),
      new KeyValue(PROP_COMM_APN_SERVER, "com.apnserv", KeyValue.STRING, SAVE, 1,
          DFT_COMM_APN_SERVER),
      new KeyValue(PROP_COMM_APN_USER, "com.apnuser", KeyValue.STRING, SAVE, 1, DFT_COMM_APN_USER),
      new KeyValue(PROP_COMM_APN_PASSWORD, "com.apnpass", KeyValue.STRING, SAVE, 1,
          DFT_COMM_APN_PASSWORD),
      new KeyValue(PROP_COMM_APN_SETTINGS, "com.apnsett", KeyValue.STRING, SAVE, 1,
          DFT_COMM_APN_SETTINGS),
      new KeyValue(PROP_COMM_CUSTOM_FORMATS, "com.custfmt", KeyValue.UINT8, SAVE, 1, "0"),
      new KeyValue(PROP_COMM_ENCODINGS, "com.encodng", KeyValue.UINT8, SAVE, 1, "7"),
      new KeyValue(PROP_COMM_BYTES_READ, "com.rdcnt", KeyValue.UINT32, SAVE, 1, "0"),
      new KeyValue(PROP_COMM_BYTES_WRITTEN, "com.wrcnt", KeyValue.UINT32, SAVE, 1, "0"),

      // --- GPS properties
      new KeyValue(PROP_GPS_SAMPLE_RATE, "gps.smprate", KeyValue.UINT16, SAVE, 1, "8"),
      new KeyValue(PROP_GPS_AQUIRE_WAIT, "gps.aquwait", KeyValue.UINT16, SAVE, 1, "0"),
      new KeyValue(PROP_GPS_EXPIRATION, "gps.expire", KeyValue.UINT16, SAVE, 1, "0"),
      new KeyValue(PROP_GPS_ACCURACY, "gps.accur", KeyValue.UDEC16, SAVE, 1, "0.0"),
      new KeyValue(PROP_GPS_MIN_SPEED, "gps.minspd", KeyValue.UDEC16, SAVE, 1, "8.0"),
      new KeyValue(PROP_GPS_DISTANCE_DELTA, "gps.dstdelt", KeyValue.UDEC32, SAVE, 1, "500.0"),

      // --- Geofence properties
      new KeyValue(PROP_CMD_GEOF_ADMIN, "geo.admin", KeyValue.COMMAND, WO, 1, null),
      new KeyValue(PROP_GEOF_COUNT, "geo.count", KeyValue.UINT16, RO, 1, "0"),
      new KeyValue(PROP_GEOF_VERSION, "geo.version", KeyValue.STRING, SAVE, 1, ""),
      new KeyValue(PROP_GEOF_ARRIVE_DELAY, "geo.arr.delay", KeyValue.UINT32, SAVE, 1, "30"),
      new KeyValue(PROP_GEOF_DEPART_DELAY, "geo.dep.delay", KeyValue.UINT32, SAVE, 1, "10"),
      new KeyValue(PROP_GEOF_CURRENT, "geo.current", KeyValue.UINT32, SAVE, 1, "0"),

      // --- GeoCorr properties
      new KeyValue(PROP_GEOC_VIOLATION_INTRVL, "gco.vio.rate", KeyValue.UINT16, SAVE, 1, "120"),
      new KeyValue(PROP_GEOC_VIOLATION_COUNT, "gco.vio.cnt", KeyValue.UINT16, SAVE, 1, "0"),
      new KeyValue(PROP_GEOC_ACTIVE, "gco.active", KeyValue.UINT32, SAVE, 1, "0"),

      // --- motion properties
      new KeyValue(PROP_MOTION_START_TYPE, "mot.start.type", KeyValue.UINT8, SAVE, 1, "0"),
      new KeyValue(PROP_MOTION_START, "mot.start", KeyValue.UDEC16, SAVE, 1, "0.0"), // kph
      new KeyValue(PROP_MOTION_IN_MOTION, "mot.inmotion", KeyValue.UINT16, SAVE, 1, "0"), // seconds
      new KeyValue(PROP_MOTION_STOP, "mot.stop", KeyValue.UINT16, SAVE, 1, "600"), // seconds
      new KeyValue(PROP_MOTION_STOP_TYPE, "mot.stop.type", KeyValue.UINT8, SAVE, 1, "0"),
      new KeyValue(PROP_MOTION_DORMANT_INTRVL, "mot.dorm.rate", KeyValue.UINT32, SAVE, 1, "0"), // seconds
      new KeyValue(PROP_MOTION_DORMANT_COUNT, "mot.dorm.cnt", KeyValue.UINT16, SAVE, 1, "1"),
      new KeyValue(PROP_MOTION_EXCESS_SPEED, "mot.exspeed", KeyValue.UDEC16, SAVE, 1, "0.0"), // kph

      // --- odometer properties
      new KeyValue(PROP_ODOMETER_0_VALUE, "odo.0.value", KeyValue.UDEC32, SAVE, 1, "0.0"),
      new KeyValue(PROP_ODOMETER_0_LIMIT, "odo.0.limit", KeyValue.UDEC32, SAVE, 1, "0.0"),
      new KeyValue(PROP_ODOMETER_0_GPS, "odo.0.gps", KeyValue.GPS, RO | SAVE, 1, "0.00000/0.00000"),
      new KeyValue(PROP_ODOMETER_1_VALUE, "odo.1.value", KeyValue.UDEC32, SAVE, 1, "0.0"),
      new KeyValue(PROP_ODOMETER_1_LIMIT, "odo.1.limit", KeyValue.UDEC32, SAVE, 1, "0.0"),
      new KeyValue(PROP_ODOMETER_1_GPS, "odo.1.gps", KeyValue.GPS, RO | SAVE, 1, "0.00000/0.00000"),
      new KeyValue(PROP_ODOMETER_2_VALUE, "odo.2.value", KeyValue.UDEC32, SAVE, 1, "0.0"),
      new KeyValue(PROP_ODOMETER_2_LIMIT, "odo.2.limit", KeyValue.UDEC32, SAVE, 1, "0.0"),
      new KeyValue(PROP_ODOMETER_2_GPS, "odo.2.gps", KeyValue.GPS, RO | SAVE, 1, "0.00000/0.00000"),

  };

  /**
   * Vector for kvord.
   */
  private static Vector kvOrd = null;

  /**
   * Name mapping.
   */
  private static Hashtable nameMap = null;

  /**
   * Code mapping.
   */
  private static Hashtable codeMap = null;

  /**
   * Default constructor, no instances are allowed.
   */
  private Props() {
    // no instance allowed
  }

  /**
   * Initialize the Props.
   */
  public static void initProps() {
    // Log.info(LOG_NAME, "Initilizing Properties ...");
    for (int i = 0; i < kvProps.length; i++) {
      Props.putKeyValue(kvProps[i]);
    }
  }

  /**
   * Load from Auxillary Store.
   * 
   * @param auxStore the Auxillary Store value.
   * @return if it has successfully completed.
   */
  public static boolean loadFromStore(AuxillaryStore auxStore) {
    Vector v = (auxStore != null) ? auxStore.readData() : null;
    if (v != null) {
      for (Enumeration e = v.elements(); e.hasMoreElements();) {
        String keyEqualsValue = (String) e.nextElement();
        Props.initFromString(keyEqualsValue); // parse "key=value"
      }
      return true;
    }
    else {
      return false;
    }
  }

  /**
   * Places the key value given a key value.
   * 
   * @param kv The key value.
   * 
   */
  public static void putKeyValue(KeyValue kv) {
    if (Props.kvOrd == null) {
      Props.kvOrd = new Vector();
    }
    if (Props.nameMap == null) {
      Props.nameMap = new Hashtable();
    }
    if (Props.codeMap == null) {
      Props.codeMap = new Hashtable();
    }
    Props.kvOrd.addElement(kv);
    Props.nameMap.put(kv.getKeyName(), kv);
    Props.codeMap.put(new Integer(kv.getKeyCode()), kv);
  }

  /**
   * Resets the key value to default values.
   */
  public static void resetToDefault() {
    for (Enumeration i = Props.getKeyValues(); i.hasMoreElements();) {
      KeyValue kv = (KeyValue) i.nextElement();
      kv.resetToDefault();
    }
  }

  /**
   * Returns the key values.
   * 
   * @return The key values.
   */
  public static Enumeration getKeyValues() {
    return Props.kvOrd.elements();
  }

  /**
   * Returns the key value given a code.
   * 
   * @param code The code.
   * @return The Key value.
   */
  public static KeyValue getKeyValue(int code) {
    if (Props.codeMap != null) {
      return (KeyValue) Props.codeMap.get(new Integer(code));
    }
    else {
      Log.error(LOG_NAME, "Not initialized!!");
      return null;
    }
  }

  /**
   * Returns the key value given a name.
   * 
   * @param name The name.
   * @return the Key value.
   */
  public static KeyValue getKeyValue(String name) {
    if (name == null) {
      return null;
    }
    else if (name.startsWith("0x") || name.startsWith("0X")) {
      return Props.getKeyValue((int) StringTools.parseHexLong(name, 0x0000L));
    }
    else if (Props.nameMap != null) {
      return (KeyValue) Props.nameMap.get(name);
    }
    else {
      Log.error(LOG_NAME, "Not initialized!!");
      return null;
    }
  }

  /**
   * Initializes a key value from a string.
   * 
   * @param key The key value.
   * @param val The string value.
   * @param setAsDefault The default value
   * @return If it has successfully been initialized.
   */
  public static boolean initFromString(int key, String val, boolean setAsDefault) {
    KeyValue kv = Props.getKeyValue(key);
    return (kv != null) ? kv.initFromString(val, setAsDefault) : false;
  }

  /**
   * Initializes a key from a string.
   * 
   * @param key The key value.
   * @param val The string value.
   * @return If it has successfully been initialized.
   */
  public static boolean initFromString(int key, String val) {
    KeyValue kv = Props.getKeyValue(key);
    return (kv != null) ? kv.initFromString(val) : false;
  }

  /**
   * Initializes a key from a string.
   * 
   * @param key The key value.
   * @param val The string value.
   * @return If it has successfully been initialized.
   */
  public static boolean initFromString(String key, String val) {
    KeyValue kv = Props.getKeyValue(key);
    return (kv != null) ? kv.initFromString(val) : false;
  }

  /**
   * Initializes a key from a string.
   * 
   * @param keyEqualsVal The string value.
   * @return If it has successfully been initialized.
   */
  public static boolean initFromString(String keyEqualsVal) {
    // Parse/init "key=value"
    String keyVal[] = KeyValue.parseKeyValue(keyEqualsVal);
    if ((keyVal != null) && (keyVal.length == 2) && (keyVal[0] != null)) {
      return Props.initFromString(keyVal[0], keyVal[1]);
    }
    else {
      return false;
    }
  }

  /**
   * Sets the changed key.
   * 
   * @param key The key
   * @return If the key has successfully been set.
   */
  public static boolean setChanged(int key) {
    KeyValue kv = Props.getKeyValue(key);
    if (kv != null) {
      kv.setChanged();
      return true;
    }
    else {
      return false;
    }
  }

  /**
   * Sets the changed key.
   * 
   * @param key The key
   * @return If the key has successfully been set.
   */
  public static boolean setChanged(String key) {
    KeyValue kv = Props.getKeyValue(key);
    if (kv != null) {
      kv.setChanged();
      return true;
    }
    else {
      return false;
    }
  }

  /**
   * Clears the changed key.
   * 
   * @param key The key value.
   * @return If the key has successfully been cleared.
   */
  public static boolean clearChanged(int key) {
    KeyValue kv = Props.getKeyValue(key);
    if (kv != null) {
      kv.clearChanged();
      return true;
    }
    else {
      return false;
    }
  }

  /**
   * Clears the changed key.
   * 
   * @param key The key value.
   * @return If the key has successfully been cleared.
   */
  public static boolean clearChanged(String key) {
    KeyValue kv = Props.getKeyValue(key);
    if (kv != null) {
      kv.clearChanged();
      return true;
    }
    else {
      return false;
    }
  }

  /**
   * Sets the key to read only.
   * 
   * @param key The key value.
   * @param readOnly The read only value.
   * @return If it has successfully been set to read only.
   */
  public static boolean setReadOnly(int key, boolean readOnly) {
    KeyValue kv = Props.getKeyValue(key);
    if (kv != null) {
      kv.setReadOnly(readOnly);
      return true;
    }
    else {
      return false;
    }
  }

  /**
   * Sets the change listener.
   * 
   * @param key The key value.
   * @param changeListener The ChangeListener value.
   * @return If it has successfully set the ChangeListener.
   */
  public static boolean setChangeListener(int key, KeyValue.ChangeListener changeListener) {
    KeyValue kv = Props.getKeyValue(key);
    if (kv != null) {
      kv.setChangeListener(changeListener);
      return true;
    }
    else {
      return false;
    }
  }

  /**
   * Sets the command handler.
   * 
   * @param key The key value
   * @param cmd The Command handler
   * @return If it has successfully been set.
   */
  public static boolean setCommandHandler(int key, KeyValue.CommandHandler cmd) {
    KeyValue kv = Props.getKeyValue(key);
    if (kv != null) {
      return kv.setCommandHandler(cmd);
    }
    else {
      return false;
    }
  }

  /**
   * Returns the pay load.
   * 
   * @param key The key value.
   * @return The payload value.
   * @throws ClientErrorException If the client produces an error
   */
  public static Payload getPayload(int key) throws ClientErrorException {
    // return the specified property in a Payload

    /* get KeyValue */
    KeyValue kv = Props.getKeyValue(key);
    if (kv == null) {
      throw new ClientErrorException(ClientErrors.ERROR_PROPERTY_INVALID_ID);
    }
    else if (kv.isWriteOnly()) {
      throw new ClientErrorException(ClientErrors.ERROR_PROPERTY_WRITE_ONLY);
    }

    /* return payload */
    Payload p = kv.getPayload(-1);
    if (p == null) {
      throw new ClientErrorException(ClientErrors.ERROR_PROPERTY_UNKNOWN_ERROR);
    }
    return p;

  }

  /**
   * Sets the payload value.
   * 
   * @param key The key value.
   * @param payload The payload value
   * @throws ClientErrorException If the client produces an error
   */
  public static void setPayload(int key, Payload payload) throws ClientErrorException {
    // parse property values from the specified Payload

    /* get KeyValue */
    KeyValue kv = Props.getKeyValue(key);
    if (kv == null) {
      throw new ClientErrorException(ClientErrors.ERROR_PROPERTY_INVALID_ID);
    }
    else if (kv.isReadOnly()) {
      throw new ClientErrorException(ClientErrors.ERROR_PROPERTY_READ_ONLY);
    }

    /* Command? */
    if (kv.isCommand()) {
      byte b[] = payload.readBytes(payload.getAvail());
      KeyValue.CommandHandler ch = kv.getCommandHandler();
      if (ch != null) {
        int cmdErr = ch.command(key, -1, b);
        if (cmdErr > 0) {
          throw new ClientErrorException(ClientErrors.ERROR_COMMAND_ERROR, cmdErr);
        }
      }
      else {
        throw new ClientErrorException(ClientErrors.ERROR_COMMAND_INVALID, 0);
      }
    }

    /* non-command */
    kv.setPayload(payload);

  }

  /**
   * Returns the byte array.
   * 
   * @param key The key value
   * @param dft The dft value
   * @return The byte array
   */
  public static byte[] getByteArray(int key, byte dft[]) {
    KeyValue kv = Props.getKeyValue(key);
    if (kv != null) {
      return kv.getByteArray(dft);
    }
    else {
      return dft;
    }
  }

  /**
   * Sets the byte array.
   * 
   * @param key The key value
   * @param val the Val array
   * @return If the byte array has successfully been set.
   */
  public static boolean setByteArray(int key, byte val[]) {
    KeyValue kv = Props.getKeyValue(key);
    if (kv != null) {
      return kv.setByteArray(val);
    }
    else {
      return false;
    }
  }

  /**
   * Returns the boolean value.
   * 
   * @param key The key value
   * @param ndx The ndx value.
   * @param dft The dft value
   * @return If the boolean value has successfully been retrieved.
   */
  public static boolean getBoolean(int key, int ndx, boolean dft) {
    KeyValue kv = Props.getKeyValue(key);
    if (kv != null) {
      return kv.getBoolean(ndx, dft);
    }
    else {
      return dft;
    }
  }

  /**
   * Sets the boolean value.
   * 
   * @param key The key value.
   * @param ndx The ndx value.
   * @param val The boolean value.
   * @return If the boolean value has been sucessfully set.
   */
  public static boolean setBoolean(int key, int ndx, boolean val) {
    KeyValue kv = Props.getKeyValue(key);
    if (kv != null) {
      return kv.setBoolean(val, ndx);
    }
    else {
      return false;
    }
  }

  /**
   * Returns the long value.
   * 
   * @param key The key value
   * @param ndx The ndx value
   * @param dft The dft value
   * @return The long value
   * 
   */
  public static long getLong(int key, int ndx, long dft) {
    KeyValue kv = Props.getKeyValue(key);
    if (kv != null) {
      return kv.getLong(ndx, dft);
    }
    else {
      return dft;
    }
  }

  /**
   * Sets the long value.
   * 
   * @param key The key value
   * @param ndx The ndx value
   * @param val The long value
   * @return If the long value has been successfully set.
   */
  public static boolean setLong(int key, int ndx, long val) {
    KeyValue kv = Props.getKeyValue(key);
    if (kv != null) {
      return kv.setLong(val, ndx);
    }
    else {
      return false;
    }
  }

  /**
   * Adds the long values.
   * 
   * @param key The key value
   * @param ndx The ndx value
   * @param val The long value
   * @return The resulting long value
   */
  public static long addLong(int key, int ndx, long val) {
    long n = Props.getLong(key, ndx, 0L) + val;
    return Props.setLong(key, ndx, n) ? n : 0L;
  }

  /**
   * Returns the double value.
   * 
   * @param key The key value
   * @param ndx The ndx value
   * @param dft The dft value
   * @return The double value
   */
  public static double getDouble(int key, int ndx, double dft) {
    KeyValue kv = Props.getKeyValue(key);
    if (kv != null) {
      return kv.getDouble(ndx, dft);
    }
    else {
      return dft;
    }
  }

  /**
   * Returns the double value.
   * 
   * @param key The key value
   * @param ndx The ndx value
   * @param val The double value
   * @return If the double value has been successfully set
   */
  public static boolean setDouble(int key, int ndx, double val) {
    KeyValue kv = Props.getKeyValue(key);
    if (kv != null) {
      return kv.setDouble(val, ndx);
    }
    else {
      return false;
    }
  }

  /**
   * Adds the double values.
   * 
   * @param key The key value
   * @param ndx The ndx value
   * @param val The double value
   * @return The resulting double value
   */
  public static double addDouble(int key, int ndx, double val) {
    double n = Props.getDouble(key, ndx, 0.0) + val;
    return Props.setDouble(key, ndx, n) ? n : 0.0;
  }

  /**
   * Returns the string value.
   * 
   * @param key The key value
   * @param dft The dft string value
   * @return The string value.
   */
  public static String getString(int key, String dft) {
    KeyValue kv = Props.getKeyValue(key);
    if (kv != null) {
      return kv.getString(dft);
    }
    else {
      return dft;
    }
  }

  /**
   * Sets the string values.
   * 
   * @param key The key value
   * @param val The string value
   * @return If the string value has successfully been set
   */
  public static boolean setString(int key, String val) {
    KeyValue kv = Props.getKeyValue(key);
    if (kv != null) {
      return kv.setString(val);
    }
    else {
      return false;
    }
  }

  /**
   * Sets the geographical point.
   * 
   * @param key The key value
   * @param dft The GeoPoint value
   * @return The geographical point
   */
  public static GeoPoint getGeoPoint(int key, GeoPoint dft) {
    KeyValue kv = Props.getKeyValue(key);
    if (kv != null) {
      return kv.getGeoPoint(dft);
    }
    else {
      return dft;
    }
  }

  /**
   * Set the geographical point.
   * 
   * @param key The key value
   * @param val The GeoPoint value
   * @return If the GeoPoint value has successfully been set.
   */
  public static boolean setGeoPoint(int key, GeoPoint val) {
    KeyValue kv = Props.getKeyValue(key);
    if (kv != null) {
      return kv.setGeoPoint(val);
    }
    else {
      return false;
    }
  }

  /**
   * Determines if the key values have changed.
   * 
   * @return True or false if the key values have changed
   */
  public static boolean hasChanged() {
    for (Enumeration i = Props.getKeyValues(); i.hasMoreElements();) {
      KeyValue kv = (KeyValue) i.nextElement();
      if (kv.hasChanged()) {
        return true;
      }
    }
    return false;
  }

  /**
   * Clears the changed values.
   * 
   */
  public static void clearChanged() {
    for (Enumeration i = Props.getKeyValues(); i.hasMoreElements();) {
      KeyValue kv = (KeyValue) i.nextElement();
      kv.clearChanged();
    }
  }

  /**
   * Saves the auxillary stores.
   * 
   * @param auxStore The auxillary stores
   * @param all Set to true or false for all auxillary stores
   * @return if successfully saved.
   */
  public static boolean saveToStore(AuxillaryStore auxStore, boolean all) {
    if (Props.hasChanged()) {
      // Include only 'savable' properties
      Log.debug(LOG_NAME, "Saving properties ...");
      Vector v = new Vector();
      for (Enumeration i = Props.getKeyValues(); i.hasMoreElements();) {
        KeyValue kv = (KeyValue) i.nextElement();
        if (kv.isSave() && !kv.isDefault()) {
          String kvStr = kv.toString();
          Log.debug(LOG_NAME, "   " + kvStr);
          v.addElement(kvStr);
        }
      }
      auxStore.writeData(v);
      Props.clearChanged();
      return true;
    }
    else {
      return false;
    }
  }

  /**
   * Prints the key values.
   * 
   * @param all True or false to print all key values
   * 
   */
  public static void print(boolean all) {
    // Include everything except commands
    StringBuffer sb = new StringBuffer();
    for (Enumeration i = Props.getKeyValues(); i.hasMoreElements();) {
      KeyValue kv = (KeyValue) i.nextElement();
      if (!kv.isCommand()) {
        sb.append(kv.toString());
        sb.append("\n");
      }
    }
    System.out.println(sb.toString());
  }

  /**
   * Interface for the Auxillary Store.
   */
  public interface AuxillaryStore {

    /**
     * Writes vector data.
     * 
     * @param rcds the vector data
     * @return if completed.
     */
    public boolean writeData(Vector rcds);

    /**
     * Reads data.
     * 
     * @return the Vector data
     */
    public Vector readData();
  }

  // ------------------------------------------------------------------------

}
